Saving and loading games with Godot
https://www.youtube.com/watch?v=43BZsLZheA4
== Table of Contents ==
00:00 Introduction
00:46 A simple approach
単純にファイルに変数を保存しておき、Load時に読み込む方法
超単純なゲームならこれでもいいが、基本的にはオススメされない
変数の管理やセーブファイルの互換性維持が難しい
その他、様々な問題が挙げられる
開発中は res:// にセーブファイルを保存できるが、export したら書き込み不可になる
なので、セーブデータは基本的に user:// に保存すること
10:03 Format choices - JSON
保存したい変数を Dictionary に入れて、JSON.stringify で json 化してファイル保存
JSON.parse_string で読み込んで、変数に再格納する
Position のような Vector2変数をそのまま書き込むと、(120, 50.5) のような文字列になる
読み込み時に Vector2 に変換し直す必要あり
https://gyazo.com/17e6d692051f230fcdb72b691c29de52
17:58 Format choices - Dictionaries
Dictionary でそのままバイナリ保存する方法
Vector2などもそのまま保存できるので、JSONより使い勝手が良い
ただ、JSONのようにテキストエディタで開いた時にデータが人間の目で確認できない
https://gyazo.com/f4bfebf3efac4816239fe7137235da45
20:33 Format choices - Resources
Godot の Resource クラスを extends して、保存したい値だけを Custom Resource 管理する方法
クラスのプロパティは補完がきくので、typoが減る
カスタムリソースを new() して値を格納後に、ResourceSaver.save で任意のパスに保存できる
パスを指定して load()し、各種値を再格納する
https://gyazo.com/e27766591fa4d395897421e471db2be2
上の JSON や Dictionary 保存時の問題が解決できる
しかし、他の問題がある。それは後ほど
26:00 Handling dynamic game elements
シーン内の動き回る敵の位置などをどう Save/Loadするか
Custom Resource に EnemyPositionsArray を追加して、それぞれの position を格納する
Load 時に、その位置で敵を生成し直す
32:43 A generalized approach
問題点1: 敵が死ぬモーションの最中にセーブされると、プレイヤーから見て死んだはずの敵が Load で生き返ってしまう
死ぬモーション中の敵に関してはセーブをしない処理が必要
その判定は、セーブスクリプトで行わない。その敵自身が、自分が今保存されるべき状態かどうかを判定してセーブして欲しい値を返す・登録する設計が良い
セーブスクリプトが全てのオブジェクトの判定をするのは大変、詳細を知りすぎるから
今回の方法では、セーブされる側のクラスに on_save_game(saved_data) 関数を追加して、自身で必要な情報を登録させる
code:gd
func _on_save_game(save_data:ArraySavedData): if _dying:
return
var my_data = SavedData.new()
my_data.position = global_position
my_data.scene_path = scene_file_path
saved_data.append(my_data)
セーブスクリプトで、上記関数を実行する
code:gd
get_tree().call_group("game_events", "on_save_game", saved_data)
ロードスクリプトでは、同様に各クラスに用意した on_before_load_game() を実行してから読み込んで、on_load_game()を実行する
48:12 Extending the generalized approach
プレイヤーが放つ魚雷も保存対象する場合
敵と同じように実装すると、魚雷は角度がおかしくなってしまった
これは、角度情報を保存していなかったから
魚雷専用の CustomSavedData をSavedData をextends して作成し、そこに角度情報を追加する
53:48 Saved game compatibility
互換性の問題
古いバージョンでセーブした後、パラメータを追加して新しいスクリプトでロードしようとするとエラーになってしまう
古いバージョンでのロードをコード上でサポートする必要性がある
新しい機能を追加すると古いセーブは簡単に壊れるので、気をつけてね
この動画では話しきれないので割愛
大型アップデートした場合は「古いセーブは消えます」的なメッセージつけて強制アップデートするしかない時もある
いつまでも古いセーブデータをサポートするのは大変
59:55 Saved game security
Godot カスタムリソースでセーブ・ロードを実装する場合の注意点
テキストフォーマットだと簡単にセーブデータを書き換えられてチートできる
ロード時に、不正な値は弾くようにバリデーションをする
セーブデータを暗号化する
ただし、暗号化キーは解析して取得される可能性がある
任意のスクリプトをインジェクトできる
これは避けるためには、やっぱりJSONやDictを使うしかないの?
なんと、これを避ける add-on があるらしい
このプラグインを有効にしてインジェクトされたセーブデータをロードすると、null が返ってくる。
ずっと Godot の Save/Load 方式は困っていたが、このプラグインを使った Custom Resource 方式が一番良いと思った。この動画を見てよかったkidooom.icon
1:07:47 Conclusion